﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using cogency.representation;

namespace cogency.languages.csharp
{
    public class CSharpWriter : CodeWriterContext
    {
        protected delegate void WriteTypeDeclaration(RDeclaration decl);
        protected Dictionary<RDeclarationType, WriteTypeDeclaration> Lookup = new Dictionary<RDeclarationType, WriteTypeDeclaration>();
        protected List<RDeclaration> DataExchangeTypes = new List<RDeclaration>();
        protected List<RDeclaration> InterfaceTypes = new List<RDeclaration>();

        public CSharpWriter(CodeWriterContext objectSrc)
            : base(objectSrc)
        {
            Lookup[RDeclarationType.EnumDeclaration] = CreateEnumDeclaration;
            Lookup[RDeclarationType.EnumReference] = CreateEnumReference;
            Lookup[RDeclarationType.BuiltinTypeReference] = CreateBuiltinTypeReference;
            Lookup[RDeclarationType.ClassDeclaration] = CreateClassDeclaration;
            Lookup[RDeclarationType.ClassReference] = CreateClassReference;
            Lookup[RDeclarationType.InterfaceDeclaration] = CreateInterfaceDeclaration;
            Lookup[RDeclarationType.InterfaceReference] = CreateInterfaceReference;
            Lookup[RDeclarationType.MethodDeclaration] = CreateMethodDeclaration;
        }

        public virtual void Process()
        {
            foreach(RDeclaration decl in Namespace.Declarations)
            {
                ProcessTopLevelDeclaration(decl);
            }

            CreateXmlDataExchange();

            CookComputingXmlRpcWriter xmlrpc = new CookComputingXmlRpcWriter(this);
            if( CodeTargetType == languages.CodeTargetType.XmlRpcClient )
            {
                foreach(RDeclaration decl in InterfaceTypes)
                {
                    xmlrpc.CreateXmlRpcClientInterfaceDeclaration(decl);
                    xmlrpc.CreateXmlRpcClientImplementation(decl);
                }
            }
            else if (CodeTargetType == languages.CodeTargetType.XmlRpcStatelessServer)
            {
                foreach (RDeclaration decl in InterfaceTypes)
                {
                    xmlrpc.CreateXmlRpcServerInterfaceDeclaration(decl);
                    xmlrpc.CreateXmlRpcClientImplementation(decl);
                }
            }
        }

        protected void CreateXmlDataExchange()
        {
            string filename = "XmlDataExchange.cs";
            string pathname = Path.Combine(RootDirectory, Namespace.Name, filename);
            Trace.TraceInformation("Creating {0}", pathname);

            using (Output = File.CreateText(pathname))
            {
                WriteCommonHeader();
                Output.Write("using System.Diagnostics;\r\n");
                Output.Write("using System.Xml.Linq;\r\n\r\n");
                BeginNamespace();

                Output.Write(Indent);
                Output.Write("internal static class XmlDataExchange\r\n");
                BeginScope();

                foreach (RDeclaration decl in DataExchangeTypes)
                {
                    CreateXmlDataExchangeToString(decl);
                    CreateXmlDataExchangeFromString(decl);
                }
                EndScope();
                EndNamespace();
            }
        }

        protected void CreateXmlDataExchangeToString(RDeclaration decl)
        {
            Append(decl, "{0}public static string StringFrom{1}({1} input)\r\n");
            BeginScope();

            if( decl.DeclarationType == RDeclarationType.EnumDeclaration )
            {
                Append(decl, "{0}return input.ToString();\r\n");
            }
            else
            {
                Append(decl, "{0}if( null == input )\r\n");
                Indent++;
                Append(decl, "{0}return \"null\";\r\n");
                Indent--;
                Append(decl, "{0}XElement result = new XElement(\"{1}\");\r\n");

                foreach (RDeclaration memberDecl in decl.Items)
                {
                    CreateXmlDataExchangeMemberToString(memberDecl);
                }
                Append(decl, "{0}return result.ToString();\r\n");
            }
            EndScope();
            Newline();
        }

        protected void CreateXmlDataExchangeFromString(RDeclaration decl)
        {
            Append(decl, "{0}public static {1} {1}FromString(string input)\r\n");
            BeginScope();
            if (decl.DeclarationType == RDeclarationType.EnumDeclaration)
            {
                Append(decl, "{0}return ({1}) System.Enum.Parse(typeof({1}), input);\r\n");
            }
            else
            {
                Append(decl, "{0}if( string.IsNullOrEmpty(input) || input.Equals(\"null\") )\r\n");
                Indent++;
                Append(decl, "{0}return null;\r\n");
                Indent--;
                Append(decl, "{0}XElement temp = XElement.Parse(input);\r\n");
                Append(decl, "{0}Trace.Assert(temp.Name.ToString().Equals(\"{1}\"));\r\n");
                Append(decl, "{0}return new {1}(\r\n");
                Indent++;
                bool first = true;
                foreach(RDeclaration param in decl.Items)
                {
                    if (first)
                        first = false;
                    else
                        Output.Write(",\r\n");
                    CreateXmlDataExchangeMemberFromString(param);
                }
                Indent--;
                Output.Write(");\r\n");
            }
            EndScope();
            Newline();
        }

        void CreateXmlDataExchangeMemberFromString(RDeclaration decl)
        {
            switch (decl.DeclarationType)
            {
                case RDeclarationType.BuiltinTypeReference:
                    switch(decl.BuiltinType)
                    {
                        case RBuiltinType.Int32:
                            Append(decl, "{0}int.Parse(temp.Element(\"{1}\").Value)");
                            break;
                        case RBuiltinType.Int64:
                            Append(decl, "{0}long.Parse(temp.Element(\"{1}\").Value)");
                            break;
                        case RBuiltinType.Boolean:
                            Append(decl, "{0}bool.Parse(temp.Element(\"{1}\").Value)");
                            break;
                        case RBuiltinType.String:
                            Append(decl, "{0}temp.Element(\"{1}\").Value");
                            break;
                    }
                    break;

                case RDeclarationType.EnumReference:
                    Append(decl, "{0}{2}FromString(temp.Element(\"{1}\").Value)");
                    break;

                default:
                    throw new InvalidOperationException(string.Format("member is not supported: {0}", decl));
            }
        }

        protected void CreateXmlDataExchangeMemberToString(RDeclaration decl)
        {
            switch (decl.DeclarationType)
            {
                case RDeclarationType.BuiltinTypeReference:
                    AppendFormat( "{0}result.Add(new XElement(\"{1}\", input.{1}));\r\n", Indent, decl.Name);
                    break;

                case RDeclarationType.EnumReference:
                    AppendFormat( "{0}result.Add(new XElement(\"{1}\", StringFrom{2}(input.{1})));\r\n", Indent, decl.Name, decl.ReferencedName);
                    break;

                default:
                    throw new InvalidOperationException(string.Format("member is not supported: {0}", decl));
            }
        }

        protected void ProcessTopLevelDeclaration(RDeclaration decl)
        {
            string filename;
            if(decl.DeclarationType == RDeclarationType.InterfaceDeclaration )
                filename = string.Format("I{0}.cs", decl.Name);
            else
                filename = string.Format("{0}.cs", decl.Name);
            string pathname = Path.Combine(RootDirectory, Namespace.Name, filename);
            Trace.TraceInformation("Creating {0}", pathname);

            using(Output = File.CreateText(pathname))
            {
                CreateTopLevelDeclaration(decl);
            }
        }

        protected static string GetCSharpScope(RDeclaration decl)
        {
            return GetCSharpScope(decl.DeclarationScope);
        }

        protected static string GetCSharpScope(RDeclarationScope scope)
        {
            switch(scope)
            {
                case RDeclarationScope.Private:
                    return "private";

                case RDeclarationScope.Protected:
                    return "protected";

                case RDeclarationScope.Public:
                    return "public";
            }
            return "";
        }

        protected void WriteComment(RDeclaration decl)
        {
            if( !string.IsNullOrEmpty(decl.Comment) )
            {
                Output.Write(Indent);
                Output.Write("/// <summary>\r\n");
                foreach(string line in decl.Comment.Split('\n'))
                {
                    Output.Write(Indent);
                    Output.Write("/// ");
                    Output.Write(line.TrimEnd());
                    Output.Write("\r\n");
                }
                Output.Write(Indent);
                Output.Write("/// </summary>\r\n");
            }
        }

        protected void WriteCommonHeader()
        {
            if( !string.IsNullOrEmpty(CommonHeader) )
            {
                foreach(string line in CommonHeader.Split('\n'))
                {
                    Output.Write("// ");
                    Output.Write(line.TrimEnd());
                    Output.Write("\r\n");
                }
                Output.Write("\r\n");
            }
        }

        protected void CreateTopLevelDeclaration(RDeclaration decl)
        {
            WriteCommonHeader();
            BeginNamespace();
            Lookup[decl.DeclarationType](decl);
            EndNamespace();
        }

        protected void CreateEnumDeclaration(RDeclaration decl)
        {
            WriteComment(decl);
            Output.Write(Indent);
            Output.Write(GetCSharpScope(decl));
            Output.Write(" enum ");
            Output.Write(decl.Name);
            Output.Write("\r\n");
            BeginScope();

            int nmax = decl.Items.Count;
            int index = 0;
            foreach (RDeclaration eval in decl.Items)
            {
                Trace.Assert(eval.DeclarationType == RDeclarationType.EnumValueDeclaration);

                WriteComment(eval);
                Output.Write(Indent);
                Output.Write(eval.Name);
                Output.Write(" = ");
                Output.Write(eval.ReferencedName);
                if (++index < nmax)
                {
                    Output.Write(",\r\n\r\n");
                }
                else
                {
                    Output.Write("\r\n");
                }
            }
            EndScope();
            DataExchangeTypes.Add(decl);
        }

        protected void CreateEnumReference(RDeclaration decl)
        {
            WriteComment(decl);
            Output.Write(Indent);
            Output.Write(GetCSharpScope(decl));
            Output.Write(" ");
            Output.Write(decl.ReferencedName);
            Output.Write(" ");
            Output.Write(decl.Name);
            Output.Write(";\r\n\r\n");
        }

        protected static string GetCSharpParameterType(RDeclaration decl)
        {
            switch(decl.DeclarationType)
            {
                case RDeclarationType.BuiltinTypeReference:
                    return GetCSharpType(decl.BuiltinType);

                case RDeclarationType.EnumReference:
                    return decl.ReferencedName;

                case RDeclarationType.ClassReference:
                    return decl.ReferencedName;
            }
            throw new InvalidOperationException(string.Format("Unable to map {0} to a parameter type", decl));
        }

        protected static string GetCSharpType(RBuiltinType t)
        {
            switch(t)
            {
                case RBuiltinType.Int32:
                    return "int";

                case RBuiltinType.Int64:
                    return "long";

                case RBuiltinType.String:
                    return "string";

                case RBuiltinType.Boolean:
                    return "bool";
            }
            throw new InvalidOperationException(string.Format("GetCSharpType fails for type {0}", t));
        }

        protected void CreateBuiltinTypeReference(RDeclaration decl)
        {
            WriteComment(decl);
            Output.Write(Indent);
            Output.Write(GetCSharpScope(decl));
            Output.Write(" ");
            Output.Write(GetCSharpType(decl.BuiltinType));
            Output.Write(" ");
            Output.Write(decl.Name);
            Output.Write(";\r\n\r\n");
        }

        protected void CreateClassReference(RDeclaration decl)
        {
            throw new NotImplementedException("CreateClassReference");
        }

        protected void CreateInterfaceDeclaration(RDeclaration decl)
        {
            WriteComment(decl);
            Output.Write(Indent);
            Output.Write(GetCSharpScope(decl));
            Output.Write(" interface I");
            Output.Write(decl.Name);
            Output.Write("\r\n");
            BeginScope();

            foreach (RDeclaration member in decl.Items)
            {
                Trace.Assert(member.DeclarationType == RDeclarationType.MethodDeclaration);
                CreateInterfaceMethodDeclaration(member);
            }
            EndScope();
            InterfaceTypes.Add(decl);
        }

        protected void CreateInterfaceMethodDeclaration(RDeclaration decl)
        {
            WriteComment(decl);
            Output.Write(Indent);
            if( decl.ReturnType == null )
            {
                Output.Write("void ");
            }
            else
            {
                Output.Write(GetCSharpParameterType(decl.ReturnType));
                Output.Write(" ");
            }
            Output.Write(decl.Name);
            if( decl.HasItems )
            {
                Output.Write("(");
                int index = 1;
                int count = decl.Items.Count;
                foreach (RDeclaration member in decl.Items)
                {
                    Output.Write(GetCSharpParameterType(member));
                    Output.Write(" ");
                    Output.Write(member.Name);
                    if (index++ < count)
                    {
                        Output.Write(", ");
                    }
                }
                Output.Write(");\r\n\r\n");
            }
            else
            {
                Output.Write("();\r\n\r\n");
            }
        }

        protected void CreateInterfaceReference(RDeclaration decl)
        {
            throw new NotImplementedException("CreateInterfaceReference");
        }

        protected void CreateMethodDeclaration(RDeclaration decl)
        {
            throw new NotImplementedException("CreateMethodDeclaration");
        }

        protected void CreateClassDeclaration(RDeclaration decl)
        {
            WriteComment(decl);
            Output.Write(Indent);
            Output.Write(GetCSharpScope(decl));
            Output.Write(" class ");
            Output.Write(decl.Name);
            Output.Write("\r\n");
            BeginScope();
            
            foreach (RDeclaration member in decl.Items)
            {
                Lookup[member.DeclarationType](member);
            }

            CreateClassExplicitConstructor(decl);
            CreateClassCopyConstructor(decl);
            CreateToString(decl);
            EndScope();

            DataExchangeTypes.Add(decl);
        }

        protected static string GetParameterName(RDeclaration decl)
        {
            string result = decl.Name.ToLower();
            if( result == decl.Name )
            {
                return "_" + result;
            }
            return result;
        }

        protected void CreateClassExplicitConstructor(RDeclaration decl)
        {
            Output.Write(Indent);
            Output.Write("/// <summary>\r\n");
            Output.Write(Indent);
            Output.Write("/// Explicit constructor\r\n");
            Output.Write(Indent);
            Output.Write("/// </summary>\r\n");
            foreach (RDeclaration member in decl.Items)
            {
                Output.Write(Indent);
                Output.Write("/// <param name=\"{0}\">{1}</param>\r\n", GetParameterName(member), member.Comment);
            }

            Output.Write(Indent);
            Output.Write("public ");
            Output.Write(decl.Name);
            Output.Write("(");
            int index = 1;
            int count = decl.Items.Count;
            foreach (RDeclaration member in decl.Items)
            {
                Output.Write(GetCSharpParameterType(member));
                Output.Write(" ");
                Output.Write(GetParameterName(member));
                if( index++ < count )
                {
                    Output.Write(", ");
                }
            }
            Output.Write(")\r\n");
            BeginScope();
            foreach (RDeclaration member in decl.Items)
            {
                Output.Write(Indent);
                Output.Write(member.Name);
                Output.Write(" = ");
                Output.Write(GetParameterName(member));
                Output.Write(";\r\n");
            }
            EndScope();
            Output.Write("\r\n");
        }

        public override string ToString()
        {
            return base.ToString();
        }

        protected void CreateToString(RDeclaration decl)
        {
            AppendIndented("/// <summary>\r\n");
            AppendIndented("/// String representation\r\n");
            AppendIndented("/// </summary>\r\n");
            AppendIndented("/// <param name=\"objectSrc\">Object to copy</param>\r\n");
            Append(decl, "{0}public override string ToString()\r\n");
            BeginScope();
            Append(decl, "{0}return XmlDataExchange.StringFrom{1}(this);\r\n");
            EndScope();
            Output.Write("\r\n");
        }
        protected void CreateClassCopyConstructor(RDeclaration decl)
        {
            AppendIndented("/// <summary>\r\n");
            AppendIndented("/// Copy constructor\r\n");
            AppendIndented("/// </summary>\r\n");
            AppendIndented("/// <param name=\"objectSrc\">Object to copy</param>\r\n");
            Append(decl, "{0}public {1}({1} objectSrc)\r\n");
            BeginScope();
            foreach (RDeclaration member in decl.Items)
            {
                Append(member, "{0}{1} = objectSrc.{1};\r\n");
            }
            EndScope();
            Output.Write("\r\n");
        }

        protected void BeginScope()
        {
            Output.Write(Indent);
            Output.Write("{\r\n");
            Indent++;
        }

        protected void EndScope()
        {
            Indent--;
            Output.Write(Indent);
            Output.Write("}\r\n");            
        }

        protected void BeginNamespace()
        {
            AppendFormat("namespace {0}.{1}\r\n{{\r\n", RootNamespace, Namespace.Name);
            Indent++;
        }

        protected void EndNamespace()
        {
            Indent--;
            Output.Write("}\r\n");
        }


    }
}
